package com.simpou.commons.utils.lang;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.codehaus.jettison.mapped.Configuration;
import org.codehaus.jettison.mapped.MappedNamespaceConvention;
import org.codehaus.jettison.mapped.MappedXMLStreamWriter;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.stream.XMLStreamWriter;
import java.io.Writer;
import java.util.*;
import com.simpou.commons.utils.reflection.Casts;
import java.io.StringReader;
import javax.xml.bind.Unmarshaller;

/**
 * 
 *
 * @author Jonas Pereira
 * @since 2013-08-20
 * @version 2013-08-20
 */
public class JaxbHelper {

    private static Set<Class<?>> contextClasses = new HashSet<Class<?>>();
    @Getter
    private static JAXBContext context;

    static {
        try {
            refreshContext();
        } catch (JAXBException ex) {
            throw new RuntimeException(ex);
        }
    }

    static Set<Class<?>> getContextClasses() {
        return Collections.unmodifiableSet(contextClasses);
    }

    public static void addClassesToContext(final Class... classes) throws JAXBException {
        List<Class<?>> list = Casts.simpleCast(Arrays.asList(classes));
        addClassesToContext(list);
    }

    public static void addClassesToContext(final Collection<Class<?>> classes) throws JAXBException {
        contextClasses.addAll(classes);
        refreshContext();
    }

    public static void addClassesToContext(final Class<?> clasz) throws JAXBException {
        contextClasses.add(clasz);
        refreshContext();
    }

    private static void refreshContext() throws JAXBException {
        //TODO incluir locks
        context = getContextFrom(contextClasses.toArray(new Class[contextClasses.size()]));
    }

    public static JAXBContext getContextFrom(final Class<?>... classes) throws JAXBException {
        return JAXBContext.newInstance(classes);
    }

    public static void marshal(final JAXBContext context, final Writer writer, final MarshalType type, final Object object, final Property... properties) throws JAXBException, Exception {
        switch (type) {
            case JSON:
                marshalJson(context, writer, object, properties);
                break;
            case XML:
                marshalXml(context, writer, object, properties);
                break;
            default:
                throw new UnsupportedOperationException("Media type " + type + " not supported yet.");
        }
    }

    public static void marshalXml(final JAXBContext context, final Writer writer, final Object object, final Property... properties) throws Exception {
        Marshaller marshaller = getMarshaller(context, properties);
        marshaller.marshal(object, writer);
    }

    public static void marshalJson(final JAXBContext context, final Writer writer, final Object object, final Property... properties) throws Exception {
        Configuration config = new Configuration();
//        config.setSupressAtAttributes(true);
//        config.setImplicitCollections(true);
        MappedNamespaceConvention con = new MappedNamespaceConvention(config);
        XMLStreamWriter xmlStreamWriter = new MappedXMLStreamWriter(con, writer);
        Marshaller marshaller = getMarshaller(context, properties);
        marshaller.marshal(object, xmlStreamWriter);
    }

    public static Marshaller getMarshaller(final JAXBContext context, final Property... properties) throws Exception {
        Marshaller marshaller = context.createMarshaller();
        for (Property property : properties) {
            marshaller.setProperty(property.getName().toString(), property.getValue());
        }
        return marshaller;
    }
    
    /**
     * Converte um xml em objeto compatível JAXB.
     * 
     * @param clasz Tipo do objeto a ser retornado.
     * @param xml Conteúdo textual em formato xml.
     * @return Objeto convertido.
     * @throws JAXBException Caso ocorra.
     */
    public static <T> T unmarshalXml(final Class<T> clasz, final String xml) throws JAXBException {
        final JAXBContext localContext = JAXBContext.newInstance(clasz);
        final Unmarshaller unmarshaller = localContext.createUnmarshaller();
        final StringReader reader = new StringReader(xml);
        final T unmarshaled = (T) unmarshaller.unmarshal(reader);
        return unmarshaled;
    }

    public enum MarshalType {

        XML, JSON;

    }

    @RequiredArgsConstructor
    @Getter
    public static class Property {

        private final Object name;
        private final Object value;

    }
}
